/*============================================================================
 * Copyright 2009 VMware Inc.
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 
 * use this file except in compliance with the License. You may obtain a copy 
 * of the License at: http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 
 * License for the specific language governing permissions and limitations 
 * under the License.
 ============================================================================*/

package com.vmware.vcloud.adapter;

import java.io.IOException;
import java.net.MalformedURLException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Vector;

import net.chrisrichardson.ec2deployer.core.AWSProperties;
import net.chrisrichardson.ec2deployer.core.AbstractCloudManager;
import net.chrisrichardson.ec2deployer.core.CloudCommandExecutor;
import net.chrisrichardson.ec2deployer.core.EC2Server;
import net.chrisrichardson.ec2deployer.core.EC2ServerHolder;
import net.chrisrichardson.ec2deployer.core.ProcessMonitor;
import net.chrisrichardson.ec2deployer.core.Ssh;

import com.vmware.vcloud.Org;
import com.vmware.vcloud.Task;
import com.vmware.vcloud.VApp;
import com.vmware.vcloud.VCloudInstance;
import com.vmware.vcloud.VDC;
import com.vmware.vcloud.data.VAppStatus;
import com.vmware.vcloud.tmrk.InternetService;
import com.vmware.vcloud.tmrk.Node;
import com.vmware.vcloud.tmrk.PublicIpAddress;

/**
 * @author Steve Jin (sjin@vmware.com)
 */
public class VCloudManager extends AbstractCloudManager implements EC2ServerFactory
{
	private EC2ServerHolder ec2Servers = new EC2ServerHolder();
	
	AWSProperties awsProps;
	private String startupScript;
	private Ssh ssh;
	private Vector<String> stopVMs = new Vector<String>();
	private VCloudInstance vCloudInstance = null;
//	private HashMap<String, String> ipMap = new HashMap<String, String>();
	
    public VCloudManager(AWSProperties awsp) 
    {
        this.awsProps = awsp;
        this.ssh = new Ssh(awsp);
        if (awsProps.exists("startupScript")) 
        {
            startupScript = (String) awsp.getProperty("startupScript");
        }
    }
	public boolean associateAddress(EC2Server arg0, String arg1) 
	{
		return false;
	}

	public EC2Server findInstance(String id) 
	{
		// in our case, we use URL as the instance ID of vApp
		String vmTemplate = (String)awsProps.getProperty("vCloud.vmTemplateURL");
		EC2Server ec2s = new EC2Server(this, id, vmTemplate, "running", "NA");
		
		VAppNetworkInfo vni = new VAppNetworkInfo(id);
		ec2s.getInstanceState().updateStatus("running", vni.publicIP, vni.privateIP);

		return ec2s;
	}

	public AWSProperties getAwsProperties() 
	{
		return this.awsProps;
	}

	public List<EC2Server> getServers() 
	{
		return this.ec2Servers.getServers();
	}

	public CloudCommandExecutor getSsh() 
	{
		return this.ssh;
	}

	public String getStartupScript() 
	{
		return this.startupScript;
	}

	public List<EC2Server> newServers(int n, String instanceType,
			Map<String, String> extraParams) 
	{
		// get current time for unique VM names
		long time = new Date().getTime();
		SimpleDateFormat sdf = new SimpleDateFormat("mmssSSS");

		List<EC2Server> ec2List = new ArrayList<EC2Server>();
		String vmTemplate = (String)awsProps.getProperty("vSphere.vmTemplatePath");

		for(int i=0; i<n; i++)
		{
			//vApp name can have only chars, numbers, -.
			//must start with a letter, and no longer than longer than 15 in length
			String vAppName = "J2EE-" + sdf.format(time) + i;
			VApp va = null;
			
			try
			{
				VCloudInstance si = connect2vCloud();
				
				String vAppTemplate = (String)awsProps.getProperty("vCloud.vmTemplateURL");
				System.out.println("vAppTempURL=" + vAppTemplate);
				
				Org[] orgs = si.getOrgs();
				VDC[] vdcs = orgs[0].getVDCs();
				
				va = vdcs[0].deployVAppFromTemplate(vAppTemplate, vAppName, vdcs[0].getNetworks()[0].getURL(), 1, 1024);
				
				va.waitFor(VAppStatus.poweredoff, 10000); // 2 -- powered off
				System.out.println("deploy is done!");
			
				// The following code tried to work around a bug
				for(;;)
				{
					try
					{
						Thread.sleep(3*1000);
						@SuppressWarnings("unused")
						Task task = va.powerOn();
					} catch(IOException ioe)
					{
						ioe.printStackTrace();
						continue;
					}
					break;
				}
				
				va.waitFor(VAppStatus.running, 2000); // 4 -- running
				System.out.println("poweron is done!");
				
				String privateIP = getIPAddr(va);
				
				// SSH routing 
				InternetService is = vdcs[0].addInternetService("VMware SpringSource CF", "TCP", 22, "Enabling SSH to the vApp");
				System.out.println("is:" + is.getXmlContent());
				Node node = is.addNode(privateIP, "vApp Node for CF", 22, "Enabling SSH to the vApp");
				System.out.println("node:" + node.getXmlContent());
				
				// HTTP routing
				PublicIpAddress pia = is.getPublicIpAddress();
				System.out.println("pia:" + pia.getXmlContent());
				InternetService webIs = pia.addInternetService("Web Service", "HTTP", 80, "Bridging the Web Server");	
				Node webNode = webIs.addNode(privateIP, "vAppNode for CF", 80, "Bridging");
				System.out.println("webNode:" + webNode.getXmlContent());
				
				String publicIP = pia.getIp();
				
				// encode the network info into the instance ID
				VAppNetworkInfo vni = new VAppNetworkInfo();
				vni.vAppUrl = va.getURL();
				vni.publicIP = publicIP;
				vni.privateIP = privateIP;
				vni.publicIpUrl = pia.getURL();
				vni.internetServiceUrls = new String[] { is.getURL(), webIs.getURL() };
				vni.nodeUrls = new String[] { node.getURL(), webNode.getURL() };
				String vaid = vni.toString();
				
				EC2Server ec2s = new EC2Server(this, vaid, vmTemplate, "running", "NA");
			
				System.out.println("public IP: " + publicIP);
				ec2s.getInstanceState().updateStatus("running", publicIP, privateIP);
				ec2List.add(ec2s);
			}catch(Exception e)
			{
				throw new RuntimeException(e);
			}
		}

		this.ec2Servers.addAll(ec2List);
		return ec2List;
	}

	public List<EC2Server> newServersElsewhere(int arg0, String arg1,
			EC2Server arg2, Map<String, String> arg3) 
	{
		System.out.println("newServersElsewhere(3) is called");
		return null;
	}

	public void pollStatus() 
	{
		try
		{
			VCloudInstance si = connect2vCloud();
			
			for(EC2Server ec2s : ec2Servers.getServers())
			{
				String vaid = ec2s.getInstanceId();
				VAppNetworkInfo vni = new VAppNetworkInfo(vaid);
				
				VApp va = new VApp(si.getRC(), vni.vAppUrl);
//				* Does not exist - status -> gone
//				* Exists and is powered on -> running
//				* Exists and is powered off -> terminated
				VAppStatus status = va.getStatus();
				
				//TODO need a way to tell the VApp is already deleted
				if(va ==null)
				{
					ec2s.updateStatus("gone", getIPAddr(va), ec2s.getInstanceId());
				}
				else if(status==VAppStatus.running)
				{
					String privateIP = ec2s.getPrivateDnsName();
					String publicIP = ec2s.getPublicDnsName();
					System.out.println("public IP: " + publicIP);
					ec2s.updateStatus("running", publicIP, privateIP);
				}
				else
				{
					ec2s.updateStatus("terminated", "0.0.0.0", "0.0.0.0");
				}
			}
		} catch(Exception e)
		{
			e.printStackTrace();
		}
	}

	public List<?> reserve(int n, String arg1) 
	{
		return null;
	}

	public void setDefaultProcessMonitor(ProcessMonitor arg0) 
	{
	}

	public void setServers(List<EC2Server> servers) 
	{
		this.ec2Servers.addAll(servers);
	}

	public void startPolling(Object interval) 
	{
		Timer timer = new Timer();
		timer.schedule(new PollTask(), 0, 15 * 1000);
	}
		
	class PollTask extends TimerTask 
	{
	    public void run() 
	    {
	    	pollStatus();
	    }
    }

	public void stop(EC2Server ec2s) 
	{
		if(stopVMs.contains(ec2s.getInstanceId()))
		{
			return;
		}
		else
		{
			stopVMs.add(ec2s.getInstanceId());
		}
		try
		{
			VCloudInstance si = connect2vCloud();
			
			String vaid = ec2s.getInstanceId();
			VAppNetworkInfo vni = new VAppNetworkInfo(vaid);
			VApp va = new VApp(si.getRC(), vni.vAppUrl);
			
			Task task = va.powerOff();
			task.waitFor(3000); //wait it to be done
			
			//delete the Nodes and InternetServices
			String[] nodeUrls = vni.nodeUrls;
			String[] isUrls = vni.internetServiceUrls;
			for(int i=0; i<nodeUrls.length; i++)
			{
				Node node = new Node(si.getRC(), nodeUrls[i]);
				node.delete();
				
				InternetService is = new InternetService(si.getRC(), isUrls[i]);
				is.delete();
			}
			//delete the public IP
			PublicIpAddress pia = new PublicIpAddress(si.getRC(), vni.publicIpUrl);
			pia.delete();
			
			// finally, delete the vApp
			va.delete();
		} catch(Exception e)
		{
			e.printStackTrace();
		}
	}
	
	private VCloudInstance connect2vCloud() throws Exception, MalformedURLException
	{
		if(vCloudInstance==null)
		{
			vCloudInstance = 
				new VCloudInstance(
				(String) awsProps.getProperty("vCloud.apiURL"), 
				(String)awsProps.getProperty("vCloud.user"), 
				(String)awsProps.getProperty("vCloud.password"));
		}
		return vCloudInstance;
	}
	
	private String getIPAddr(VApp va) throws InterruptedException, IOException
	{
		for(;;)
		{
			Thread.sleep(3*1000);
			String ip = va.getIpAddress();
			if(ip!=null)
			{
				System.out.println("ip addr:" + ip);
				return ip;
			}
		}
	}
	@Override
	public EC2Server makeEC2Server(String instanceId, String imageId,
			String state, String availabilityZone) 
	{
		// TODO Auto-generated method stub
		return null;
	}
}
